import DynamicLink from "../../../src/components/DynamicLink";

# 视频处理

工作中遇到两个视频处理和播放相关的需求：

1. 一个视频 list，产品预期是用户进入页面自动播放
2. 可支持用户上传视频，对于视频的部分内容进行抽帧，以图片的格式上传给后端
3. 以上两个需求需要同时兼容 PC 端和移动端

## 自动播放

### autoplay

一开始先实现 PC 端的部分，用的是 html5 中的 video 标签，加了一个`autoplay`的属性。在 PC 端 Chrome 视频是可以自动播放的，整体的体验也还不错，但是`autoplay`一到移动端的页面就全乱了，尤其移动端的 Safari，`autoplay`的实现居然是自动弹窗全屏播放视频，一个视频列表弹出几十个视频，用户体验非常糟糕。

### 视频取帧上传

最简单的技术方案是基于 video 标签 + canvas 绘制，但是这里也遇到了自动播放的问题。video 标签中的视频必须先播放才能从里边截图。

其他视频库比如 video.js 和 MediaElement.js 视频库基本上都基于上面的技术方案进行的封装。

还有一个技术方案是基于 ffmpeg，当时也探索了这个方案，但是会有些问题。 详情：<DynamicLink
  path="/docs/Frontend/实践问题总结/ffmpeg"
  content="ffmpeg探索实践"
/>

| 方案                             | 描述                                                       | 优点                                                           | 缺点                                                                             |
| -------------------------------- | ---------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| 纯 JavaScript + HTML5 Video      | 使用 HTML5 的 `<video>` 元素和 JavaScript 控制播放和取帧。 | 简单易用，无需额外库，广泛支持于主流浏览器。                   | 各浏览器间的视频特性和自动播放策略存在差异，可能影响跨浏览器的一致性和用户体验。 |
| 第三方视频库（如 Video.js）      | 使用如 Video.js 这样的第三方库来提供视频播放和处理功能。   | 开发简化，兼容性好，封装了复杂的视频操作。                     | 本质上仍受限于浏览器对 HTML video 的支持和限制。                                 |
| 前端 WebAssembly (Wasm) + FFmpeg | 在浏览器中使用 Wasm 版本的 FFmpeg 进行视频处理。           | 功能强大，可以进行复杂的视频处理，超越 HTML video 元素的限制。 | 增加复杂度和加载时间，需要考虑 Wasm 的兼容性和性能。                             |

#### video 元素 + canvas 实现视频取帧

主要是获取到 video DOM 元素，通过`play()`方法在后台自动播放。之后都简称：video.play()

```js
const processVideo = (videoSrc) => {
  const video = document.createElement("video");
  video.src = videoSrc;
  video.muted = true;
  video.autoplay = true;

  return new Promise((resolve) => {
    video.onloadedmetadata = () => {
      video.play().then(() => resolve(video));
    };
  });
};

const captureVideoFrame = (video) => {
  const canvas = document.createElement("canvas");
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  const ctx = canvas.getContext("2d");
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
  return canvas.toDataURL("image/png");
};

function handleUploadVideo(selectedFile) {
  const src = URL.createObjectURL(selectedFile);
  processVideo(src)
    .then(captureVideoFrame)
    .then((frame) => {
      // 这里可以处理抽取到的帧，例如上传到服务器
      console.log("Captured frame:", frame);
    });
}
```

核心的两个操作：

```js
// 播放
video.play().then(() => {
  resolve(video);
});

// 绘制
ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
```

#### 缺陷

1. 移动端部分浏览器的`autoplay`会自动弹窗全屏播放，体验很差，autoplay 的支持依赖内核和网络状况，比如 iPhone 在蜂窝网络下明确禁用了 autoplay；
2. 移动端浏览器下，video.play()在没有明确的用户操作下，video.play()也是无法激活播放的；

| 体验                  | PC 端 Chrome | PC 端 Safari | 移动端 Chrome | 移动端 Safari |
| --------------------- | ------------ | ------------ | ------------- | ------------- |
| autoplay              | 良好         | 无法自动播放 | 会有弹窗      | 会有弹窗      |
| 通过 DOM 元素控制播放 | 允许         | 禁止         | 禁止          | 禁止          |

## 解决方案

### autoplay

1. 对于`autoplay`自动播放产品设计上是强需求，没办法修改，因此对于移动端的场景全部采用图片`gif`格式（几乎所有浏览器都兼容）代替视频`mp4`格式

2. 如果产品上可以妥协，那么可以添加一个交互按钮，等到用户点击播放按钮之后再播放(这里需要注意的是浏览器原生的播放器并不好用)

### video.play

这里采用“隐藏视频元素”的方案代替 video.play(); “隐藏视频元素”具体指的是在页面上渲染长宽都为 1px 的 video 元素，并且静音，在用户上传视频以后添加一个“引导按钮”，比如：Continue 之类的标识，本质上是让用户点击以后触发视频播放。
这里不选择 display:none 而是采用 1px 是为了避免视频元素无法激活。

上述的自定义用户播放按钮代替原生浏览器的视频播放器也可以这样实现。

## 待完善的点

1. 考虑使用 apng 代替 gif，gif 每个像素只有 8bit，最多支持 256 种颜色，导致画质比较差

## 参考

[移动端 HTML5 video 视频播放优化实践](https://www.xuanfengge.com/h5-video-play.html)

[apng、webp 适用性调研与兼容性方案](https://lizheguang.github.io/zhaiGuang/2020/10/10/gitwebp/)
